#!/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'wshu'
__version__ = '1.0'
"""
    ***********************************
    *  @filename : hashers.py
    *  @Author : wshu
    *  @CodeDate : 2020/4/19 21:23
    *  @Software : PyCharm
    ***********************************
"""
import hmac
import string
import hashlib
import secrets
import binascii
import random, time
from typing import Tuple

__SECRET_KEY__ = b'n248!m-$1+xdgin0^926ispy(_$a@4*w^%p_c0@(&6t8_b7=j*'

UNUSABLE_PASSWORD_PREFIX = '!'  # This will never be a valid encoded hash
UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40  # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
DEFAULT_ENTROPY = 16  # number of bytes to return by default

def is_password_usable(encoded):
    """
    Return True if this password wasn't generated by
    User.set_unusable_password(), i.e. make_password(None).
    """
    return encoded is None or not encoded.startswith(UNUSABLE_PASSWORD_PREFIX)

def make_password(password: str):
    """
    :param password:
    :return: 生成加盐密码
    """
    if password is None:
        return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
    salt = secrets.token_bytes(16).hex().encode('ascii')
    hash_res = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'),
                                  salt, 100000)
    hash_res = binascii.hexlify(hash_res)
    return (salt + hash_res).decode('ascii')

def check_password(stored_password: bytes, provided_password: str) -> bool:
    """
    :param plain_text_password: 原始密文
    :param hashed_password:  加密后密文
    :return: 验证是否一样
    """
    if provided_password is None or not is_password_usable(provided_password):
        return False
    salt = stored_password[:32]
    stored_password = stored_password[32:]
    hash_res = hashlib.pbkdf2_hmac('sha256',
                                   provided_password.encode('utf-8'),
                                   salt.encode('ascii'),
                                   100000)
    hash_res = binascii.hexlify(hash_res).decode('ascii')
    return hmac.compare_digest(hash_res, stored_password)




def encryp(string: str, user: str, salt_len=None) -> [bytes, bytes]:
    """
    :param string:
    :return: 加密函数
    """
    if isinstance(user, str):
        user = bytes(user, encoding='utf-8')
    SECRET_KEY = user + __SECRET_KEY__
    if salt_len is None:
        _salt_len = DEFAULT_ENTROPY
    else:
        _salt_len = salt_len
    salt_val = secrets.token_bytes(_salt_len)
    # 采用一样的KEY来验证
    hash_res = hashlib.pbkdf2_hmac('sha256', string.encode('utf-8'), SECRET_KEY, 100000)
    encryp_str = hash_res.hex()
    return encryp_str


def make_safe_url():
    """
    :param string:
    :return: 随机url token
    """
    urlarg = secrets.token_urlsafe(16)
    return urlarg

def get_random_string(length=12,
                      allowed_chars=string.ascii_letters + string.digits):
    """
    Return a securely generated random string.

    The default length of 12 with the a-z, A-Z, 0-9 character set returns
    a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
    """
    random.seed(
        hashlib.sha256(
            ('%s%s%s' % (random.getstate(), time.time(), __SECRET_KEY__)).encode()
        ).digest()
    )
    return ''.join(random.choice(allowed_chars) for i in range(length))

def identify_hasher(encoded):
    """
    用作检测密码识别
    """
    if ((len(encoded) == 32 and '$' not in encoded) or
            (len(encoded) == 37 and encoded.startswith('md5$$'))):
        algorithm = 'unsalted_md5'
    else:
        algorithm = encoded.split('$', 1)[1]
    return algorithm